﻿using System;
using System.Collections;
using System.Collections.Generic;

namespace Mclib
{
	/// <summary>
	/// This class is useful for displaying numbers in a way that is as short (concise) as possible.
	/// </summary>
	public class NiceNumberMaker
	{
		/// <summary>
		/// When 1 significant digit is required, this helps with rounding.
		/// This constant can be added to the log10 of a number so that its log10 is in the appropriate range
		/// before the rounding to the nearest desired power of 10.
		/// Truncation error is accounted for and this errs on the side of rounding up when truncation precision is approached.
		/// </summary>
		public readonly double Log10Rounder1 = 1.0-Math.Log10(9.499999999999999);
		/// <summary>
		/// When 2 significant digits are required, this helps with rounding.
		/// This constant can be added to the log10 of a number so that its log10 is in the appropriate range
		/// before the rounding to the nearest desired power of 10.
		/// Truncation error is accounted for and this errs on the side of rounding up when truncation precision is approached.
		/// </summary>
		public readonly double Log10Rounder2 = 1.0-Math.Log10(9.949999999999999);
		/// <summary>
		/// When 3 significant digits are required, this helps with rounding.
		/// This constant can be added to the log10 of a number so that its log10 is in the appropriate range
		/// before the rounding to the nearest desired power of 10.
		/// Truncation error is accounted for and this errs on the side of rounding up when truncation precision is approached.
		/// </summary>
		public readonly double Log10Rounder3 = 1.0-Math.Log10(9.994999999999999);
		/// <summary>
		/// When 4 significant digits are required, this helps with rounding.
		/// This constant can be added to the log10 of a number so that its log10 is in the appropriate range
		/// before the rounding to the nearest desired power of 10.
		/// Truncation error is accounted for and this errs on the side of rounding up when truncation precision is approached.
		/// </summary>
		public readonly double Log10Rounder4 = 1.0-Math.Log10(9.999499999999999);
		/// <summary>
		/// Creates a nice short string (with at least 2 significant digits) for displaying a number.  If the third significant digit is a 5, that is also definitely displayed.
		/// </summary>
		/// <param name="x">The number to be displayed.</param>
		/// <returns>A string displaying the value of x.</returns>
		public string ShortString(double x)
		{
			return ShortString(x,double.PositiveInfinity);
		}
		/// <summary>
		/// Creates a nice short string (with at least 2 significant digits) for displaying a number without
		/// violating the specified tolerance.  If the third significant digit is a 5, that is also displayed.
		/// </summary>
		/// <param name="x">The number to be displayed.</param>
		/// <param name="tolerance">The displayed number must be discernable to changes of this magnitude or greater.</param>
		/// <returns>A string displaying the value of x.</returns>
		public string ShortString(double x,double tolerance)
		{
			if (x==0.0)
				return "0";
			if (double.IsNaN(tolerance))
				tolerance = double.PositiveInfinity;

			string txt=null;
			string negChar = "";
			if (x<0.0)
				negChar = "-";

			//	x = n * 10^s
			double s = Math.Log10(Math.Abs(x));
			//	Incremented to make truncation for 2 significant digits workable.
			double s2 = s + Log10Rounder2;
			//	Incremented to make truncation for 3 significant digits workable.
			double s3 = s + Log10Rounder3;
			//	Are we are required to use the 2-digit rounding?  (If 3 digit rounding brings us to another power of 10, we cannot.)
			bool mustUse2 = (int)s3 != (int)s2;
			//	Tolerance comparator
			double tol;
			if (mustUse2)
				tol = Math.Log10(0.0199999999999999*tolerance);
			else
				tol = Math.Log10(0.0049999999999999*tolerance);

			if (s2<=tol) // If true, string length is determined by 
			{
				//	The first 3 characters of this string are the 3 significant digits.
				if (mustUse2)
					txt = ((int)Math.Pow(10.0,(s2%1.0)+3.0)).ToString();
				else
					txt = ((int)Math.Pow(10.0,(s3%1.0)+3.0)).ToString();
				if (s2>=3.0 || s2<-3.0)
				{
					// abs(x) is greater or equal to 1000 (3) OR abs(x) is less than .001 (-3)
					if (mustUse2 || txt[2]!='5')
						if (txt[1]=='0')
							return negChar+	txt[0]						+"e"+ ShortString(Math.Floor(s2));
						else
							return negChar+	txt[0] +"."+txt[1]			+"e"+ ShortString(Math.Floor(s2));
					else
						return negChar+	txt[0] +"."+txt[1]+txt[2]	+"e"+ ShortString(Math.Floor(s2));
				}
				if (s2<0.0)
				{
					// abs(x) is less than 1.0 (0) and greater or equal to 0.001 (-3)
					if (s2<-2.0)
					{
						//	abs(x) is less than 0.01 (-2) and greater or equal to 0.001 (-3)
						if (mustUse2 || txt[2]!='5')
							if (txt[1]=='0')
								return negChar+"0.00"+txt[0];
							else
								return negChar+"0.00"+txt[0]+txt[1];
						else
							return negChar+"0.00"+txt[0]+txt[1]+txt[2];
					}
					if (s2<-1.0)
					{
						//	abs(x) is less than 0.1 (-1) and greater or equal to 0.01 (-2)
						if (mustUse2 || txt[2]!='5')
							if (txt[1]=='0')
								return negChar+"0.0"+txt[0];
							else
								return negChar+"0.0"+txt[0]+txt[1];
						else
							return negChar+"0.0"+txt[0]+txt[1]+txt[2];
					}
					//	abs(x) is less than 1.0 (0) and greater or equal to 0.1 (-1)
					if (mustUse2 || txt[2]!='5')
						if (txt[1]=='0')
							return negChar+"0."+txt[0];
						else
							return negChar+"0."+txt[0]+txt[1];
					else
						return negChar+"0."+txt[0]+txt[1]+txt[2];
				}
				else
				{
					//	abs(x) is greater or equal to 1.0 (0) less than 1000 (3)
					if (s2<1.0)
					{
						//	abs(x) is greater or equal to 1.0 (0) less than 10 (1)
						if (mustUse2 || txt[2]!='5')
							if (txt[1]=='0')
								return negChar+	txt[0].ToString();
							else
								return negChar+	txt[0] +"."+ txt[1];
						else
							return negChar+	txt[0] +"."+ txt[1]+txt[2];
					}
					if (s2<2.0)
					{
						//	abs(x) is greater or equal to 10 (1) less than 100 (2)
						if (mustUse2 || txt[2]!='5')
							return negChar+	txt.Substring(0,2);
						else
							return negChar+	txt.Substring(0,2) +"."+ txt[2];
					}
					//	abs(x) is greater or equal to 100 (2) less than 1000 (3)
					return negChar+	txt.Substring(0,3);
				}
			}
			throw new NotImplementedException("TO DO");
		}
	}
}